package debtbundler.domain.bll.solver;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;

import debtbundler.domain.entity.Debt;
import debtbundler.domain.entity.Person;
import debtbundler.domain.entity.impl.EntityManager;

/**
 * A solver that tries to minimize number of debts by matching debtors 
 * and creditors in random orders. 
 * 
 * @author Anssi
 *
 */
public class RandomizedSolver extends SolverSkeleton {
	private static Logger logger = Logger.getLogger(RandomizedSolver.class.getName());
	
	private int numIter = 10000;
	private List<Debt> currentDebts = new ArrayList<Debt>();
	private Random randomGenerator = new Random();
	
	/**
	 * Creates an empty solver. Call <tt>solve(Collection)</tt> to start calculation.
	 * 
	 */ 
	public RandomizedSolver() {
	}	
	
	/**
	 * Solves debts of given persons with the default 10000 iterations.
	 * 
	 * @param persons	persons whose debts to calculate
	 */
	public RandomizedSolver(Collection<Person> persons) {
		solve(persons);
	}

	@Override
	protected void calculateSolution() {
		logger.log(Level.FINER, "#Debtors: {0}, #creditors {1}", new Object[]{debtors.size(), creditors.size()});
		
		this.debts = null; // nullify to know if no debts yet calculated
		
		for (int i=1; i<=numIter; i++) {
			logger.log(Level.FINER, "Iteration {0}", i);
			
			List<Person> randDebtors = new ArrayList<Person>(this.debtors);
			Collections.shuffle(randDebtors, randomGenerator);
			List<Person> randCreditors = new ArrayList<Person>(this.creditors);
			Collections.shuffle(randCreditors, randomGenerator);
			
			while (!(randDebtors.isEmpty() || randCreditors.isEmpty())) {
				
				
				Person debtor = randDebtors.get(0); 
				double totalToPay = Math.abs(debtor.getBalance());
				Person creditor = randCreditors.get(0);
				double totalToGet = Math.abs(creditor.getBalance());
				
				logger.log(Level.FINER, "Matching debtor {0} ({1}) and creditor {2} ({3})", new Object[]{debtor, debtor.getBalance(), debtor, creditor.getBalance()});
				
				Debt debt = EntityManager.createDebt(debtor, creditor);
				if (totalToGet > totalToPay) {
					debt.setValue(totalToPay);
					randDebtors.remove(debtor);
				} else if (totalToGet < totalToPay) {
					debt.setValue(totalToGet);
					randCreditors.remove(creditor);
				} else {
					debt.setValue(totalToGet);
					randDebtors.remove(debtor);
					randCreditors.remove(creditor);
				}
				
				currentDebts.add(debt);
				logger.log(Level.FINER, "Created debt {0}", debt);
			}
			
			if (debts == null || currentDebts.size() < debts.size()) {
				debts = new ArrayList<Debt>(currentDebts);
				logger.log(Level.FINE, "Found better solution, now {0} debts", debts.size());
			} else {
				logger.log(Level.FINE, "Could not find a better solution, still {0} debts", debts.size());
			}
			
			for (Debt debt : currentDebts) {
				debt.getDebtor().removeTransaction(debt);
			}
			currentDebts.clear();
			
			logger.log(Level.FINER, "Iteration {0} finished", i);
		}
		
		// Because we removed the debts from debtors and creditors after each iteration,
		// we need to add the debts of the best solution back
		if (debts == null) {
			debts = new ArrayList<Debt>(); 
		}
		for (Debt debt : debts) {
			debt.getDebtor().addDebt(debt);
		}

	}

	/**
	 * Returns the number of randomized iterations when solving debts.
	 * The default is 10000.
	 * 
	 * @return	number of iterations when solving debts
	 */
	public int getNumIterations() {
		return numIter;
	}

	/**
	 * @param numSamples	number of iterations when solving debts
	 */
	public void setNumIterations(int numSamples) {
		this.numIter = numSamples;
	}
	
	

}
